package org.example.utlis;

import javafx.application.Platform;
import javafx.collections.ObservableList;
import org.apache.commons.io.FileUtils;
import org.example.Device;
import org.example.appstart.StartAppInfo;
import org.example.constant.AdbConstant;

import java.io.*;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.ArrayList;

public class CmdUtils {
    private static CmdUtils cmd;
    private final String TAG = "CmdUtils: ";
    private final String TAG_ERROR = "CmdUtils: ERROR -> ";
    private final String FINDSTR = "findstr";

    public OnExeCmdListener onExeCmdListener;
    public interface OnExeCmdListener {
        void onSucceed(String msg);
        void onFail(String msg);
    }
    public interface OnDevicesListener {
        void onDevicesRead(ArrayList<Device> devices);
    }

    public static void init(OnExeCmdListener onExeCmdListener) {
        cmd = new CmdUtils(onExeCmdListener);
    }

    /**
     * call init first!
     * @return
     */
    public static CmdUtils getInstance() {
        return cmd;
    }

    private CmdUtils(OnExeCmdListener onExeCmdListener) {
        this.onExeCmdListener = onExeCmdListener;
    }

    /**
     * 获取设备及设备基本信息
     */
    public void getDevicesAsync(OnDevicesListener onDevicesListener) {
        new Thread(() -> {
            String sDevices = exe(AdbConstant.DEVICES);
            ArrayList<Device> devices = new ArrayList<>();

            //System.out.println(sDevices);
            String[] split = sDevices.split("\n");

            if (split == null || split.length == 0) {
                String msg = "getDevice: some thing wrong!";
                devices.add(new Device(msg,msg));
                onExeCmdListener.onFail(msg);
            } else if (split.length == 1) {
                String msg = "there is no device connected!";
                devices.add(new Device(msg,msg));
                onExeCmdListener.onFail(msg);
            } else {
                for (int i = 1; i < split.length; i++) {
                    String line = split[i];
                    if (line.contains("device")) {
                        devices.add(new Device(line.replace("device", "").trim(), "conneced"));
                    } else if (line.contains("unauthorized")) {
                        devices.add(new Device(line.replace("unauthorized", "").trim(), "allow usb debugging?"));
                    } else {
                        devices.add(new Device(line, "please check it!"));
                    }
                }
                for (Device device : devices) {
                    new Thread(() -> {
                        device.setCpus(getCpuInfo(device.getDeviceId()));
                        device.setRam(getRamTotal(device.getDeviceId()));
                        onExeCmdListener.onSucceed(device.toString());
                    }).start();
                }
            }
            onDevicesListener.onDevicesRead(devices);
        }).start();
    }

    private String getRamTotal(String deviceId) {
        String memInfo = exeBackground(getShellCmd(deviceId, AdbConstant.MEM_INFO));
        String[] memInfoArray = memInfo.split("\n");
        String memTotal = "";
        for (String s : memInfoArray) {
            if (s.contains("MemTotal:")) {
                memTotal = s.replace("MemTotal:", "").trim();
                break;
            }
        }
        return memTotal;
    }

    private ArrayList<Integer> getCpuInfo(String deviceId) {
        String coreResult = exeBackground(getShellCmd(deviceId, AdbConstant.CPU_CORE_COUNT));
        int count = Integer.valueOf(coreResult.replace("0-", "")) + 1;
        ArrayList<Integer> cpus = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            cpus.add(Integer.valueOf(exeBackground(getShellCmd(deviceId, AdbConstant.getAdbCpuMaxFreq(i)))));
        }
        return cpus;
    }

    /**
     * 耗时操作
     * @param pkgName
     * @param time 次数
     */
    public void exeJitAsync(String device, String pkgName, int time, int mode) {
        new Thread(() -> {
            String msg;
            String pkg = (pkgName == null || pkgName.isEmpty()) ? getPkgNameCurrentFocus(device) : pkgName; // 传入空则获取当前前台应用
            String result = "dex2oat " + pkg + " { ";
            String adbCmd = "";
            if (mode == 1) {
                adbCmd = AdbConstant.JIT_PREFIX;
            } else if (mode == 2) {
                adbCmd = AdbConstant.JIT_PREFIX_SPEED;
            }
            if (time == 1) {
                result += exe(getShellCmd(device, adbCmd + pkg)) + " }";
                msg = result;
            } else {
                StringBuffer sb = new StringBuffer(result);
                for (int i = 0; i < time; i++) {
                    sb.append((i+1) + ": " + exe(getShellCmd(device, AdbConstant.JIT_PREFIX + pkg)) + "; ");
                }
                msg = sb + "}";
            }
            onExeCmdListener.onSucceed(device + ": jit result: " + msg);
        }).start();
    }

    /**
     * 用户通过键入发起的adb命令,默认不包含设备号
     * @param cmd
     */
    public void exeByUser(String cmd) {
        new Thread(() -> {
            onExeCmdListener.onSucceed(exe(cmd));
        }).start();
    }

    /**
     * 接收含有设备的adb命令
     * @param cmd
     * @return
     */
    private String exe(String cmd, boolean isTellUser) {
        if (cmd == null && cmd.isEmpty()) {
            return TAG_ERROR + "the command is empty!";
        } else {
            return exeCmd(cmd, isTellUser);
        }
    }

    private String exe(String cmd) {
        return exe(cmd, true);
    }

    private String exeBackground(String cmd) {
        return exe(cmd, false);
    }

    private String exeCmd(String cmd, boolean isTellUser) {
        if (isTellUser) onExeCmdListener.onSucceed("exeCmd: " + cmd);
        boolean isFindStr = false;
        String find = null;
        if (cmd.contains(FINDSTR)) {
            String[] splits = cmd.split("\\|");
            cmd = splits[0];
            find = splits[1].replace(FINDSTR, "").trim();
            isFindStr = true;
        }
        Runtime runtime = Runtime.getRuntime();
        try {
            //System.out.println("start exe cmd: " + cmd);
            final Process process = runtime.exec(cmd);
            BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));

            String line;
            StringBuilder b = new StringBuilder();
            while ((line = br.readLine()) != null) {
                if (!isFindStr || line.contains(find)) {
                    b.append(line).append("\n");
                    if (isFindStr) {
                        onExeCmdListener.onSucceed(line);
                        process.destroy();
                    }
                }
            }
            return b.toString().trim();
        } catch (Exception e) {
            return null;
        }
    }

    public void exeStartYlog(String device) {
        exe(getShellCmd(device, AdbConstant.START_YLOG));
    }

    public void exePullYlogAsync(String device, String savePath) {
        new Thread(() -> {
            String saveFolderName = TimeUtils.getCurrentTimeForFileName() + "-" + device + "-ylog";//文件夹命名 YYYYMMdd-HH:mm:ss-deviceId-ylog
            String targetFolder = savePath + "\\" + saveFolderName;
            File directory = new File(targetFolder);
            try {
                FileUtils.forceMkdir(directory);
            } catch (Exception e) {
                onExeCmdListener.onSucceed(e.getMessage());
            }
            String command = getPullCmd(device, AdbConstant.getPullYlogCmd(targetFolder));
            onExeCmdListener.onSucceed(command);
            String result = exe(command);
            //解析结果
        }).start();
    }

    public void exeGetApkAsync(String device, String savePath) {
        new Thread(() -> {
            String pkgNameCurrentFocus = getPkgNameCurrentFocus(device);
            String fullCmd = getShellCmd(device, AdbConstant.getPmListPackage(pkgNameCurrentFocus));
            String cmdResult = exe(fullCmd);
            String apkPath = "";
            if (cmdResult == null || cmdResult.isEmpty()) {
                onExeCmdListener.onFail(device + "");
            } else {
                apkPath = cmdResult.replace("package:", "").replace("=" + pkgNameCurrentFocus, "").trim();
                exe(getPullCmd(device, apkPath + " " + savePath));
                onExeCmdListener.onSucceed(device + ": pull apk ok!");
            }
        }).start();
    }

    public void exeDumpPkgInfo(String device, String savePath) {
        exeDumpPkgInfo(device, savePath, getPkgNameCurrentFocus(device));
    }

    public void exeDumpPkgInfo(String device, String savePath, String pkgName) {
        String saveFilePath = savePath + "/" + TimeUtils.getCurrentTimeForFileName() + "-" + device + "-" + pkgName + ".txt";
        String result = exe(getShellCmd(device, AdbConstant.getDumpPackage(pkgName)));
        try {
            FileUtils.writeStringToFile(FileUtils.getFile(saveFilePath), result, Charset.defaultCharset());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void exeScreenRecordAsync(String device) {
        new Thread(() -> {
            String fullCmd = getShellCmd(device, AdbConstant.SCREEN_RECORD + AdbConstant.SCREEN_RECORD_PATH);
            exe(fullCmd);
        }).start();
    }

    public void exeStopScreenRecord(String device) {
        String screenRecordPid = getScreenRecordPid(device);
        exe(getShellCmd(device, AdbConstant.KILL + screenRecordPid));
    }

    public void exePullScreenRecord(String device, String savePath) {
        //命名格式： savePath/YYYYMMdd-HH-mm-ss-device.mp4
        String pullCmd = getPullCmd(device, AdbConstant.SCREEN_RECORD_PATH + savePath + "/" + TimeUtils.getCurrentTimeForFileName() + "-" + device + ".mp4");
        exe(pullCmd);
    }

    private String getScreenRecordPid(String device) {
        String fullCmd = getShellCmd(device, AdbConstant.PS_SCREEN_RECORD);
        String exeResult = exe(fullCmd, false);
        if (exeResult == null || exeResult.isEmpty()) {
            return null;
        }
        //shell        28300 17794 11268740 26352 futex_wait_queue_me 0 S screenrecord
        String[] s = exeResult.split(" ");
        if (s.length >= 2) {
            return s[1];
        } else {
            return null;
        }
    }

    public void exePullScreenCap(String device, String savePath) {
        //命名格式： savePath/YYYYMMdd-HH-mm-ss-device.mp4
        String pullCmd = getPullCmd(device, AdbConstant.SCREEN_CAP_PATH + savePath + "/" + TimeUtils.getCurrentTimeForFileName() + "-" + device + ".png");
        exe(pullCmd);
    }

    public void exeScreenCap(String device) {
        String fullCmd = getShellCmd(device, AdbConstant.SCREEN_CAP + AdbConstant.SCREEN_CAP_PATH);
        exe(fullCmd);
    }

    public void exeTracePerfettoAndPullAsync(String device, int duration, String savePath, int times, String tags) {
        new Thread(() -> {

            for (int i = 0; i < times; i++) {
                String traceCmd = getShellCmd(device, AdbConstant.getTracePerfetto(duration, tags));
                exe(traceCmd);

                //pull
                String pullCmd = getPullCmd(device, AdbConstant.TRACE_PERFETTO_PATH + savePath + "/" + TimeUtils.getCurrentTimeForFileName() + "-" + device + "-trace");
                exe(pullCmd);
            }
        }).start();
    }

    /**
     * 用于应用启动时间的获取和解析
     * @param device 设备
     * @param startAppInfoList 应用列表
     * @param isColdStart 是否冷启动
     * @param isBack 热启动时的返回方式 back or home
     * @param isRemove 是否比times多测两次并去除最高最低
     * @param times 单个应用测试次数
     */
    public void exeAppStartAsync(String device, ObservableList<StartAppInfo> startAppInfoList, boolean isColdStart, boolean isBack, boolean isRemove, int times) {
        new Thread(() -> {
            //adb logcat -c
            String fullLogcatClearCmd = getFullCmd(device, AdbConstant.LOGCAT, "-c");
            //adb logcat | findstr Displayed
            String fullLogcatDisplayedCmd = getFullCmd(device, AdbConstant.LOGCAT, AdbConstant.LOGCAT_FIND_STR_DISPLAYED);

            for (StartAppInfo startAppInfo : startAppInfoList) {
                String fullForceStopCmd = getShellCmd(device, AdbConstant.getForceStop(startAppInfo.getPkgName()));//adb -s deviceid shell am force-stop pkgName
                String fullStartCmd = getShellCmd(device, AdbConstant.getAppStart(startAppInfo.getMainActivity()));//adb -s deviceid shell start -W mainactivity
                //确认返回的方式：forcestop or back or home
                String fullBackCmd = isColdStart ? fullForceStopCmd : isBack ? getShellCmd(device, AdbConstant.INPUT_BACK) : getShellCmd(device, AdbConstant.INPUT_HOME);
                //1.强制关闭
                exe(fullForceStopCmd);
                sleep(2000);
                //2.先启动一次，不记录时间
                exe(fullStartCmd);
                sleep(2000);

                long delay = isColdStart ? 3500 : 2000;
                int[] data = new int[times];
                for (int i = 0; i < times; i++) {
                    //3.测试第i次
                    exe(fullBackCmd);
                    sleep(2000);
                    /*  1.adb -s deviceId logcat -c
                        2.启动
                        3.adb -s deviceId logcat | findstr Displayed
                        4.从返回结果中解析包含包名的第一行中的时间
                     */
                    exe(fullLogcatClearCmd);
                    sleep(300);

                    exe(fullStartCmd);

                    final String startData = exe(fullLogcatDisplayedCmd);
                    final String[] split = startData.split("\n");
                    for (String line : split) {
                        if (line.contains(startAppInfo.getPkgName())) {
                            final String[] datas = line.split(":");//08-03 07:28:52.712  1155  1184 I ActivityTaskManager: Displayed com.android.settings/.Settings: +796ms
                            String sData = datas[datas.length - 1];// +796ms  +1s96ms  +1s196ms
                            sData = sData.trim().replace("+", "");//796ms 1s96ms 1s196ms
                            sData = sData.replace("ms", "");//796 1s96 1s196
                            if (sData.contains("s")) {
                                //1s96 1s196
                                final String[] sAndSs = sData.split("s");
                                int second = Integer.valueOf(sAndSs[0]);//1
                                int ms = Integer.valueOf(sAndSs[1]);//96
                                data[i] = second * 1000 + ms;
                            } else {
                                //796
                                data[i] = Integer.valueOf(sData);
                            }
                        }
                    }
                    sleep(delay);
                }
                //4.求均值
                double average;
                int sum = 0;
                if (isRemove) {
                    int min = Integer.MAX_VALUE;
                    int max = Integer.MIN_VALUE;

                    for (int i = 0; i < data.length; i++) {
                        sum += data[i];
                        if (data[i] > max) {
                            max = data[i];
                        }
                        if (data[i] < min) {
                            min = data[i];
                        }
                    }
                    average = (double) (sum - min - max) / (data.length - 2);
                    //5.输出结果
                    onExeCmdListener.onSucceed(device + ", " + (isColdStart ? "冷" : "热") + "启动" + startAppInfo.getAlias() + times + "次， 数据为：" + intArray2String(data) + ", 去除最高和最低数据后均值为: " + average + "ms");
                } else {
                    for (int item : data) {
                        sum += item;
                    }
                    average = (double) sum / data.length;
                    onExeCmdListener.onSucceed(device + ", " + (isColdStart ? "冷" : "热") + "启动" + startAppInfo.getAlias() + times + "次， 数据为：" + intArray2String(data) + ", 均值为: " + average + "ms");
                }
            }
        }).start();
    }

    private void sleep(long mills){
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private String intArray2String(int[] array) {
        String str = "[";
        String split = ", ";
        for (int i = 0; i < array.length; i++) {
            str += array[i] + "ms";
            if (i < array.length - 1) {
                str += split;
            } else {
                str += "]";
            }
        }
        return str;
    }

    public String[] getPkgNameCurrentFocusAndLauncherActivity(String device) {
        String[] result = new String[2];
        String cmdResult = exe(getShellCmd(device, AdbConstant.CURRENT_PACKAGE));
        String pkgName = getPkgNameFromCmdResult(cmdResult);
        //通过package信息获取全类名
        String exe = exe(getShellCmd(device, AdbConstant.getDumpPackage(pkgName)));
        String[] lines = exe.split("\n");
        String launcherActivity = "something wrong";
        for (int i = 0; i < lines.length; i++) {
            String line = lines[i];
            if (line == null || line.isEmpty()) continue;
            if (line.contains("Category: \"android.intent.category.LAUNCHER\"")) {
                //往上解析四行
                for (int j = 1; j < 5; j++) {
                    String findLine = lines[i-j]; // f158424 ins.story.unfold/com.ufotosoft.home.SplashActivity filter 3d90553
                    if (findLine == null || findLine.isEmpty() || !findLine.contains(pkgName+"/")) continue;
                    String[] splits = findLine.split(pkgName);
                    String activity = splits[1].split("filter")[0];
                    launcherActivity = pkgName + activity;
                    break;
                }
                break;
            }
        }

        result[0] = pkgName;
        result[1] = launcherActivity;
        onExeCmdListener.onSucceed(launcherActivity);
        return result;
    }

    public String getPkgNameCurrentFocus(String device) {
        return getPkgNameFromCmdResult(exe(getShellCmd(device, AdbConstant.CURRENT_PACKAGE)));
    }

    private String getPkgNameFromCmdResult(String cmdResult) {
        // mCurrentFocus=Window{b1b44d6 u0 com.android.chrome/com.google.android.apps.chrome.Main}
        /* mCurrentFocus=Window{4b65b3c u0 com.google.android.youtube/com.google.android.apps.youtube.app.watchwhile.
        WatchWhileActivity}-[Surface(name=*Title#1115, isValid = true)/@0xdfdc0c1 - alpha=0.0 layer=0 shown=false]
         */
        String[] splits = cmdResult.split(" ");
        for (String item : splits) {
            if (item.contains(".")) {
                return (item.split("/"))[0];
            }
        }

        return "mCurrentFocus";
    }

    private String getActivityFromCmdResult(String cmdResult) {
        // mCurrentFocus=Window{b1b44d6 u0 com.android.chrome/com.google.android.apps.chrome.Main}
        String[] splits = cmdResult.split(" ");
        String activity = splits[splits.length - 1];
        return activity.replace("}", "");
    }

    private String getShellCmd(String device, String shellCmd) {
        return getFullCmd(device, AdbConstant.SHELL, shellCmd);
    }

    /**
     * 补全pull命令
     * @param device
     * @param pullCmd 需要pull的机器中文件路径和PC中目标路径，即adb pull 后面的所有内容
     * @return
     */
    private String getPullCmd(String device, String pullCmd) {
        return getFullCmd(device, AdbConstant.PULL, pullCmd);
    }

    /**
     * 补全adb命令
     * @param device
     * @param function pull 或者 shell等
     * @param cmd function后面的参数，如 adb shell pm ... 中的 pm ...
     * @return
     */
    private String getFullCmd(String device, String function, String cmd) {
        return AdbConstant.ADB + "-s " + device + function + cmd;
    }
}
